# -*- coding: utf-8 -*-

"""
Anontations:
    ldict: list dict [{'a': 1, 'b': 2}, {'c': 3, 'd': 4}]
    dic: dict
"""

def trans(dic, v, extra={}):
    """
    translate v in data
    call lambda if v is callable
    """
    if hasattr (v, '__call__'):
        return v(dic, extra)
    elif isinstance(dic, dict):
        return dic.get(v, '');
    elif isinstance(dic, list):
        return dic[v]

def map_one(maps, dic, extra={}):
    """
    map dict to a new dict according to key maps
    """
    kk = {k:trans(dic, v, extra) for k, v in maps.items()}
    return kk

def field_map(maps, ldict, extra={}):
    """
    map dict list to new dict list
    maps: dict
    ldict: dict list
    extra: dict list
    """
    return [map_one(maps, dic, extra) for dic in ldict]

def keep(dic, keeps=[]):
    """
    filter dict
    return new dict with keys in keeps
    return origin dict if keeps is empty
    """
    return {k: v for k, v in dic.items() if k in keeps}

def keeps(ldict, keeps=[]):
    """
    filter list dict
    """
    return [keep(dic, keeps) for dic in ldict]

def kick(dic, kicks=[]):
    """
    filter dict
    return new dict with keys in not in kicks
    return origin dict if kicks is empty
    """
    return {k: v for k, v in dic.items() if k not in kicks}

def kicks(ldict, kicks=[]):
    """
    filter list dict
    """
    return [kick(dic, kicks) for dic in ldict]

def index_by(ldict, key, on=[]):
    return {dic.get(key): keep(dic, on) for dic in ldict}

def list_diff(gone, gtwo):
    """
    get new modified delete changes of two groups
    gone = ('a', 'b', 'c')
    gtwo = ('b', 'c', 'd')
    ret = {'new': ('d',), 'delete': ('a'), 'keep': ('b', 'c')}
    """
    gnew = tuple(set(gtwo) - set(gone))
    gdelete = tuple(set(gone) - set(gtwo))
    gkeep = tuple(set(gone) & set(gtwo))
    return {'new': gnew, 'delete': gdelete, 'keep': gkeep}

if __name__ == '__main__':
    datas = [
        {'id': 1, 'name': 'zb', 'gender': 'male', 'class_a': 90, 'class_b': 60},
        {'id': 2, 'name': 'sy', 'gender': 'female', 'class_a': 90, 'class_b': 60},
    ]
    maps = {
        'user_id': 'id',
        'username': 'name',
        'gender': lambda data, extra: data['name'] + '_' + data['gender'],
        'rate': lambda data, extra: data['class_a']/data['class_b'],
    }
    extra = {
        'rankage': {
            1:2,
            2:4
        }
    }
    rank = {'rank': lambda data, extra: extra.get('rankage', '--').get(data['id'], '--')}
    d1 = field_map({**maps, **rank}, datas, extra)
    #print(d1)
    assert d1[0].get('rank') == 2, 'field_map failed!'

    d2 = keeps(datas, ['id', 'name'])
    #print(d2)
    assert len(d2[0]) == 2, 'keeps failed!'

    d3 = kicks(datas, ['id', 'name'])
    #print(d3)
    assert len(datas[0]) - len(d3[0]) <= 2, 'kicks failed!'

    d4 = index_by(datas, 'id', ['name'])
    #print(d4)
    assert d4[1]['name'] == 'zb', 'index_by faield!'
